using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Functions;
using FernUniHagen.CompilerExtension.Interfaces;
using FernUniHagen.CompilerExtension.Values;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;

namespace FernUniHagen.CompilerExtension.Adaptor
{
    public abstract class DeclarationBase : IDeclaration<CommonSyntaxNode>
    {
        #region fields
        private ISemanticModel _semanticModel;
        AttributeType _multiplicityType;
        GenericNameSyntax _genericNameSyntax;
        #endregion

        #region Properties
        public ISemanticModel SemanticModel
        {
            get { return _semanticModel; }
            set { _semanticModel = value; }
        }

        /// <summary>
        /// The Multiplicity kind of the given declaration
        /// </summary>
        /// <returns>the multiplicity type</returns>
        public AttributeType MultiplicityType
        {
            get { return _multiplicityType; }
            set { _multiplicityType = value; }
        }

        /// <summary>
        /// Returns null if the type is not generic. Otherwise it returns the generic type
        /// </summary>
        /// <returns>the generic type of the given declaration or null if there isn't any</returns>
        public GenericNameSyntax GenericName {
            get
            {
                return _genericNameSyntax;
                //AttributeType type = AttributeType.None;
                //GenericNameSyntax genericName = null;
                //GetMultiplicityAttribute(out type, out genericName);
                //return genericName;
            }
            set { _genericNameSyntax = value; }
        }
        #endregion

        #region constructor
        protected void Initialize()
        {
            AttributeType multiplicity = AttributeType.None;
            GenericNameSyntax genericName = null;
            GetMultiplicityAttribute(out multiplicity, out genericName);

            MultiplicityType = multiplicity;
            GenericName = genericName;
        }
        #endregion

        #region abstract methods
        #region Declaration
        public abstract TypeSyntax GetTypeSyntax();
        public abstract SyntaxList<AttributeListSyntax> GetAttributes();
        public abstract string GetName();
        #endregion

        #region Multiplicity Conversion
        public abstract StatementReplacement<CommonSyntaxNode> ConvertToMutiplicity(string newVariableName);
        #endregion
        #endregion

        #region Functions
        private AttributeType GetMultiplicityAttribute()
        {
            AttributeType multyType = AttributeType.None;
            GenericNameSyntax genericName = null;

            GetMultiplicityAttribute(out multyType, out genericName);
            return multyType;
        }

        /// <summary>
        /// Converts AttributeList to multiplicity type and genericNameSyntax if possible
        /// </summary>
        /// <param name="attributesList">List of attributes</param>
        /// <param name="multiplicityType">the type of multiplicity (any...)</param>
        /// <param name="genericName">genericname syntax in case of any</param>
        /// <returns>returns a list of errors found</returns>
        private List<string> GetMultiplicityAttribute(SyntaxList<AttributeListSyntax> attributesList, TypeSyntax type, out AttributeType multiplicityType,
            out GenericNameSyntax genericName)
        {
            List<string> errors = new List<string>();
            genericName = null;
            multiplicityType = AttributeType.None;
            List<AttributeSyntax> attributesFound = new List<AttributeSyntax>();
            foreach (var attrListSyntax in attributesList)
            {
                foreach (var attribute in attrListSyntax.Attributes)
                {
                    switch (attribute.Name.ToString())
                    {
                        case Constants.AttributeName.Any:
                        case Constants.AttributeName.AnyAttribute:
                            multiplicityType = AttributeType.Any;
                            attributesFound.Add(attribute);
                            genericName =
                                (GenericNameSyntax)(attribute.ArgumentList.Arguments.First().Expression as TypeOfExpressionSyntax).Type;
                            //TODO: do a descend check
                            var typeSym = this.SemanticModel.GetTypeInformation(genericName);
                            if (typeSym.Interfaces.ToList().Any(i => i.Name.Contains("IDictionary")))
                            //  if(genericName.ToString().Contains("Dictionary"))
                            {
                                //Convert to dictionary
                                var keyFieldName = attribute.ArgumentList.Arguments[1];
                                var keyFieldDefinition = this.SemanticModel.FindDecleration(keyFieldName.Expression as IdentifierNameSyntax);
                                var declaration = SyntaxElementFactory.GetDeclaration(keyFieldDefinition, SemanticModel);
                                var keyType = declaration.GetTypeSyntax();
                                //SemanticModel.FindDecleration()
                                genericName = FernUniHagen.CompilerExtension.Functions.Utilities.CreateGenericDictionaryType(keyType, type, genericName);

                            }
                            else
                            {
                                genericName = FernUniHagen.CompilerExtension.Functions.Utilities.CreateGenericType(type, genericName);

                            }
                            //if(typeSym.OriginalDefinition)
                            //{

                            //}

                            break;
                        case Constants.AttributeName.Option:
                        case Constants.AttributeName.OptionAttribute:
                            multiplicityType = AttributeType.Option;
                            attributesFound.Add(attribute);
                            break;
                        case Constants.AttributeName.Bare:
                        case Constants.AttributeName.BareAttribute:
                            multiplicityType = AttributeType.Bare;
                            attributesFound.Add(attribute);
                            break;


                    }
                }
            }
            if (attributesFound.Count == 0)
            {
                errors.Add("No multiplicity attribute found on this item");
            }
            else if (attributesFound.Count > 1)
            {
                errors.Add("Too many multiplicity attribute found on this item");
            }
            return errors;
        }

        /// <summary>
        /// Gets information about the multiplicity of the node
        /// </summary>
        /// <param name="multiplicityType">the type of multiplicity (any...)</param>
        /// <param name="genericName">genericname syntax in case of any</param>
        /// <returns>returns a list of errors found</returns>
        public List<string> GetMultiplicityAttribute(out AttributeType multiplicityType, out GenericNameSyntax genericName)
        {
            var type = GetTypeSyntax();
            var attributes = GetAttributes();

            List<string> errors = GetMultiplicityAttribute(attributes, type, out multiplicityType, out genericName);
            return errors;
        }
        #endregion
    }
}
